import React, { Component } from 'react';
import {
    Editor, EditorState, Modifier, RichUtils, Entity, CompositeDecorator
} from 'draft-js';
import 'draft-js/dist/Draft.css';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import { convertFromHTML, convertToHTML } from 'draft-convert';
import Modal from './Modal';
import FontPicker from './FontPicker';
import ColorPicker from './ColorPicker';
import s from './Editor.less';

// 自定义编辑器样式
const basicStyleMap = {
    BOLD: { fontWeight: 'bold' },
    ITALIC: { fontStyle: 'italic' },
    UNDERLINE: { textDecoration: 'underline' },
};
const colorStyleMap = {
    BLUE: { color: '#3388ff' },
    GREEN: { color: '#06bc07' },
    ORANGE: { color: '#ffb123' },
    RED: { color: '#ab2212' },
    DRAKRED: { color: '#680000' },
    GEAY: { color: '#586b95' },
    BLACK: { color: '#000000' },
};
const fontStyleMap = {
    FONT12: { fontSize: 12 },
    FONT14: { fontSize: 14 },
    FONT16: { fontSize: 16 },
    FONT18: { fontSize: 18 },
    FONT20: { fontSize: 20 },
    FONT22: { fontSize: 22 },
};

const customStyleMap = {
    ...basicStyleMap,
    ...colorStyleMap,
    ...fontStyleMap,
};

const colorStyleKeys = Object.keys(colorStyleMap);
const fontStyleKeys = Object.keys(fontStyleMap);

// 防止意外会退或关闭标签页
const preventUnload = e => {
    const message = '少侠留步，有内容没保存!';
    if (e) {
        e.returnValue = message;
    }
    return message;
};

// 防止backspace导致浏览器后退
const preventBackspace = e => {
    if (e.keyCode === 8 && !(e.target.isContentEditable || e.target.nodeName === 'INPUT')) {
        e.preventDefault();
    }
};

const rgb2hex = rgbColor => {
    const match = rgbColor.match(/(\d+),\s?(\d+),\s?(\d+)/);
    return match ? match.slice(1, 4).reduce((prev, current) => {
        let number = Number(current).toString(16);
        if (number.length < 2) {
            number = `0${number}`;
        }
        return `${prev}${number}`;
    }, '#') : null;
};

const Controls = ({
    editorState, onToggle, styleMap, ...otherProps
}) => {
    const currentStyle = editorState.getCurrentInlineStyle();
    const toggle = type => e => {
        e.preventDefault();
        onToggle(type);
    };
    return (
        <div {...otherProps}>
            {Object.keys(styleMap).map(type => {
                const active = currentStyle.has(type);
                // 这里多包了两层，外层为了比较大的点击区域
                // 内层为了安卓下同一个元素背景颜色+圆角的渲染问题，有可能是公共样式导致
                return (
                    <span key={type} className={s.controlButtonContainer} onClick={toggle(type)}>
                        <span className={classnames(s.controlButton, { [s.active]: active })}>
                            <span className={s[type]} />
                        </span>
                    </span>
                );
            })}
        </div>
    );
};

function findLinkEntities(contentBlock, callback, contentState) {
    contentBlock.findEntityRanges(character => {
        const entityKey = character.getEntity();
        return entityKey !== null && contentState.getEntity(entityKey).getType() === 'LINK';
    }, callback);
}

const Link = props => {
    const { url } = props.contentState.getEntity(props.entityKey).getData();
    return <a href={url}>{props.children}</a>;
};

// 从HTML生成ContentState
const getContentStateFromHTML = convertFromHTML({
    htmlToStyle: (nodeName, node, currentStyle) => {
        let nextStyle = currentStyle;

        if ((nodeName === 'span' && node.style.fontWeight === 'bold') || nodeName === 'b' || nodeName === 'strong') {
            nextStyle = nextStyle.add('BOLD');
        }
        if ((nodeName === 'span' && node.style.fontStyle === 'italic') || nodeName === 'i') {
            nextStyle = nextStyle.add('ITALIC');
        }
        if ((nodeName === 'span' && node.style.textDecoration === 'underline') || nodeName === 'u') {
            nextStyle = nextStyle.add('UNDERLINE');
        }
        if (nodeName === 'span' && node.style.color) {
            const color = node.style.color.startsWith('#') ? node.style.color : rgb2hex(node.style.color);
            const colorStyle = colorStyleKeys.find(key => colorStyleMap[key].color === color);
            if (colorStyle) {
                nextStyle = nextStyle.add(colorStyle);
            }
        }
        if (nodeName === 'span' && node.style.fontSize) {
            const fontStyle = fontStyleKeys.find(
                key => fontStyleMap[key].fontSize === Number.parseInt(node.style.fontSize, 10)
            );
            if (fontStyle) {
                nextStyle = nextStyle.add(fontStyle);
            }
        }
        return nextStyle;
    },
    htmlToEntity: (nodeName, node) => {
        if (nodeName === 'a') {
            return Entity.create('LINK', 'MUTABLE', { url: node.href });
        }
        return null;
    },
});

class DraftEditor extends Component {
    constructor(props) {
        super(props);

        const decorator = new CompositeDecorator([
            {
                strategy: findLinkEntities,
                component: Link,
            },
        ]);

        const contentState = getContentStateFromHTML(props.content);
        const editorState = props.content
            ? EditorState.createWithContent(contentState, decorator)
            : EditorState.createEmpty(decorator);
        this.state = {
            editorState,
            showFontPicker: false,
            showLinkInput: false,
            linkText: '',
            linkURL: '',
        };

        this.focus = () => {
            this.editor.focus();
        };
        // 解决初次渲染光标位置问题
        this.initFocus = () => {
            this.focus();
            this.setState({
                editorState: EditorState.moveFocusToEnd(this.state.editorState),
            });
        };
    }

    componentDidMount() {
        this.initFocus();
        window.addEventListener('popstate', preventUnload);
        window.addEventListener('beforeunload', preventUnload);
        window.addEventListener('keydown', preventBackspace);
    }

    componentWillUnmount() {
        window.removeEventListener('popstate', preventUnload);
        window.removeEventListener('beforeunload', preventUnload);
        window.removeEventListener('keydown', preventBackspace);
    }

    handleEditorChange = editorState => {
        // 忽略失去焦点的change
        if (!editorState.getSelection().getHasFocus() && !this.state.showLinkInput) {
            return;
        }

        this.setState({ editorState });
    };

    handleFocus = () => {
        this.props.onFocus && this.props.onFocus();
    };

    handleBlur = () => {
        this.props.onBlur && this.props.onBlur();
    };

    toggleBasicStyle = toggledStyle => {
        const { editorState } = this.state;
        const selection = editorState.getSelection();
        let nextEditorState = editorState;
        if (selection.isCollapsed()) {
            // 获得一个没有任何样式的contentState
            const nextContentState = Object.keys(basicStyleMap).reduce(
                (contentState, style) => Modifier.removeInlineStyle(contentState, selection, style),
                editorState.getCurrentContent()
            );

            nextEditorState = EditorState.push(editorState, nextContentState, 'change-inline-style');
        }
        nextEditorState = RichUtils.toggleInlineStyle(nextEditorState, toggledStyle);
        this.setState({
            editorState: nextEditorState,
        });
    };

    getToggledStyleEditorState = styleMap => toggledStyle => {
        const { editorState } = this.state;
        const selection = editorState.getSelection();

        // 获得一个没有任何样式的contentState
        const nextContentState = Object.keys(selection.isCollapsed() ? customStyleMap : styleMap).reduce(
            (contentState, style) => Modifier.removeInlineStyle(contentState, selection, style),
            editorState.getCurrentContent()
        );

        let nextEditorState = EditorState.push(editorState, nextContentState, 'change-inline-style');

        const currentStyle = editorState.getCurrentInlineStyle();

        // 移除掉同类型样式
        if (selection.isCollapsed()) {
            nextEditorState = currentStyle.reduce(
                (state, style) => (Object.keys(styleMap).includes(style)
                    ? RichUtils.toggleInlineStyle(state, style) : state),
                nextEditorState
            );
        }

        // 应用样式
        if (!currentStyle.has(toggledStyle)) {
            nextEditorState = RichUtils.toggleInlineStyle(nextEditorState, toggledStyle);
        }
        return nextEditorState;
    };

    toggleColor = color => {
        const nextEditorState = this.getToggledStyleEditorState(colorStyleMap)(color);
        this.setState({
            editorState: nextEditorState,
        });
    };

    showFontPicker = () => {
        this.setState({ showFontPicker: true });
    };

    hideFontPicker = () => {
        this.setState({ showFontPicker: false });
    };

    handeFontChange = (type, shouldClosePicker) => {
        const nextEditorState = this.getToggledStyleEditorState(fontStyleMap)(type);
        this.setState({
            editorState: nextEditorState,
            showFontPicker: !shouldClosePicker,
        });
    };

    showLinkInput = () => {
        const { editorState } = this.state;
        const selectionState = editorState.getSelection();
        if (selectionState.isCollapsed()) {
            this.setState({
                showLinkInput: true,
                linkURL: '',
                linkText: '',
            });
        } else {
            const contentState = editorState.getCurrentContent();
            const startKey = selectionState.getStartKey();
            const endKey = selectionState.getEndKey();
            const startOffset = selectionState.getStartOffset();
            const endOffset = selectionState.getEndOffset();
            const blockWithLinkAtBeginning = contentState.getBlockForKey(startKey);
            const linkKey = blockWithLinkAtBeginning.getEntityAt(startOffset);

            let url = '';
            if (linkKey) {
                const linkInstance = contentState.getEntity(linkKey);
                url = linkInstance.getData().url;
            }
            // 多个block只拿第一个
            const currentContentBlock = contentState.getBlockForKey(startKey);
            const selectedText = currentContentBlock
                .getText()
                .slice(startOffset, startKey === endKey ? endOffset : currentContentBlock.getLength());

            this.setState({
                showLinkInput: true,
                linkURL: url,
                linkText: selectedText,
            });
        }
    };

    handlLinkTextChange = e => {
        this.setState({ linkText: e.target.value });
    };

    handlLinkURLChange = e => {
        this.setState({ linkURL: e.target.value });
    };

    hideLinkInput = () => {
        this.setState({ showLinkInput: false });
    };

    handleLinkInputKeyDown = e => {
        // 链接输入框确认
        if (this.state.showLinkInput && e.keyCode === 13) {
            e.stopPropagation();
            e.preventDefault();
            this.handleLinkConfirm();
        }
        // 链接输入框取消
        if (this.state.showLinkInput && e.keyCode === 27) {
            this.hideLinkInput();
        }
    };

    handleLinkConfirm = () => {
        const { editorState, linkURL, linkText } = this.state;

        // 根据文本内容长度重新设置选区
        let selectionState = editorState.getSelection();
        const startKey = selectionState.getStartKey();
        const endKey = selectionState.getEndKey();
        const isBackward = selectionState.getIsBackward();
        // selection横跨多个block则修改为只选中第一个
        if (startKey !== endKey) {
            const firstBlockLength = editorState
                .getCurrentContent()
                .getBlockForKey(startKey)
                .getLength();
            selectionState = selectionState.set(isBackward ? 'anchorKey' : 'focusKey', startKey);
            selectionState = selectionState.set(isBackward ? 'anchorOffset' : 'focusOffset', firstBlockLength);
        }
        let nextEditorState = EditorState.forceSelection(editorState, selectionState);

        const contentState = nextEditorState.getCurrentContent();
        let contentStateWithEntity = contentState.createEntity('LINK', 'MUTABLE', { url: linkURL, contiguous: false });

        // 根据最新输入修改当前selection文本
        contentStateWithEntity = Modifier.replaceText(contentStateWithEntity, selectionState, linkText);

        selectionState = nextEditorState.getSelection();
        if (isBackward) {
            selectionState = selectionState.set('anchorOffset', selectionState.getFocusOffset() + linkText.length);
        } else {
            selectionState = selectionState.set('focusOffset', selectionState.getAnchorOffset() + linkText.length);
        }

        const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
        nextEditorState = EditorState.set(editorState, { currentContent: contentStateWithEntity });

        nextEditorState = RichUtils.toggleLink(nextEditorState, selectionState, entityKey);
        // 应用样式
        const currentStyle = editorState.getCurrentInlineStyle();
        nextEditorState = currentStyle.reduce(
            (state, style) => RichUtils.toggleInlineStyle(state, style),
            nextEditorState
        );
        // 给链接一个默认underline样式
        if (!nextEditorState.getCurrentInlineStyle().has('UNDERLINE')) {
            nextEditorState = RichUtils.toggleInlineStyle(nextEditorState, 'UNDERLINE');
        }

        this.setState(
            {
                editorState: nextEditorState,
                showLinkInput: false,
                linkURL: '',
            },
            this.focus
        );
    };

    handleBeforeInput = char => {
        const { editorState } = this.state;
        const newEditorState = this.handleContiguousEntity(char, editorState);
        if (newEditorState) {
            this.setState({ editorState: newEditorState });
            return 'handled';
        }
        return 'not-handled';
    };

    handleContiguousEntity = (char, editorState) => {
        const selectionState = editorState.getSelection();
        let contentState = editorState.getCurrentContent();
        const startKey = selectionState.getStartKey();
        const block = contentState.getBlockForKey(startKey);
        // handle the extending of mutable entities
        if (selectionState.isCollapsed()) {
            const startOffset = selectionState.getStartOffset();
            if (startOffset > 0) {
                // we are not at the start of the block
                const entityKeyBeforeCaret = block.getEntityAt(startOffset - 1);
                const entityKeyAfterCaret = block.getEntityAt(startOffset);
                const isCaretOutsideEntity = entityKeyBeforeCaret !== entityKeyAfterCaret;
                if (entityKeyBeforeCaret && isCaretOutsideEntity) {
                    const entity = contentState.getEntity(entityKeyBeforeCaret);
                    const isMutable = entity.getMutability() === 'MUTABLE';
                    const { contiguous = true } = entity.getData();
                    // if entity is mutable, and caret is outside, and contiguous is set the false
                    // remove the entity from the current char
                    if (isMutable && !contiguous) {
                        // insert the text into the contentState
                        contentState = Modifier.insertText(
                            contentState,
                            selectionState,
                            char,
                            editorState.getCurrentInlineStyle(),
                            null
                        );
                        // push the new content into the editor state
                        const newEditorState = EditorState.push(editorState, contentState, 'insert-characters');
                        return newEditorState;
                    }
                }
            }
        }
        return false;
    };

    // 实例方法，暴露给外部
    getCurrentHTML = () => {
        const contentState = this.state.editorState.getCurrentContent();
        return convertToHTML({
            styleToHTML(style) {
                if (Object.keys(customStyleMap).includes(style)) {
                    return <span style={customStyleMap[style]} />;
                }
                return null;
            },
            blockToHTML(/* block */) {
                return {
                    element: <p />,
                    empty: (
                        <p>
                            <br />
                        </p>
                    ),
                };
            },
            entityToHTML: (entity, originalText) => {
                if (entity.type === 'LINK') {
                    const url = /^(http:|https:|)\/\//.test(entity.data.url)
                        ? entity.data.url
                        : `http://${entity.data.url}`;
                    return <a href={url}>{originalText}</a>;
                }
                return originalText;
            },
        })(contentState);
    };

    render() {
        const { editorState } = this.state;
        const { tools } = this.props;
        return (
            <div className={s.root}>
                <div className={s.controls}>
                    <Controls
                        className={s.basicControl}
                        styleMap={basicStyleMap}
                        editorState={editorState}
                        onToggle={this.toggleBasicStyle}
                    />
                    <div className={s.tools}>
                        {tools.font && <span className={s.font} onClick={this.showFontPicker} />}
                        {tools.link && <span className={s.link} onClick={this.showLinkInput} />}
                    </div>
                    <ColorPicker
                        styleMap={colorStyleMap}
                        editorState={editorState}
                        onToggle={this.toggleColor}
                        defaultType="BLACK"
                    />
                    {this.state.showFontPicker && (
                        <FontPicker
                            styleMap={fontStyleMap}
                            editorState={editorState}
                            onChange={this.handeFontChange}
                            onClose={this.hideFontPicker}
                        />
                    )}
                </div>
                <div className={s.editor} onClick={this.focus}>
                    <div className={s.inner}>
                        <Editor
                            stripPastedStyles
                            customStyleMap={customStyleMap}
                            editorState={editorState}
                            onChange={this.handleEditorChange}
                            onFocus={this.handleFocus}
                            onBlur={this.handleBlur}
                            placeholder={this.props.placeholder || ''}
                            handleBeforeInput={this.handleBeforeInput}
                            ref={node => {
                                this.editor = node;
                            }}
                        />
                    </div>
                </div>
                <Modal open={this.state.showLinkInput} onClose={this.hideLinkInput}>
                    <div className={s.linkContainer}>
                        <h2 className={s.title}>添加链接</h2>
                        <div className={s.input}>
                            <input
                                type="text"
                                placeholder="请输入文字"
                                value={this.state.linkText}
                                onChange={this.handlLinkTextChange}
                                onKeyDown={this.handleLinkInputKeyDown}
                            />
                        </div>
                        <div className={s.input}>
                            <input
                                type="text"
                                placeholder="请输入链接地址"
                                value={this.state.linkURL}
                                onChange={this.handlLinkURLChange}
                                onKeyDown={this.handleLinkInputKeyDown}
                            />
                        </div>
                        <div className={s.actions}>
                            <span className={s.cancel} onClick={this.hideLinkInput}>
                                取消
                            </span>
                            <span className={s.confirm} onClick={this.handleLinkConfirm}>
                                确定
                            </span>
                        </div>
                    </div>
                </Modal>
            </div>
        );
    }
}

DraftEditor.defaultProps = {
    content: '',
    placeholder: '',
    onFocus: null,
    onBlur: null,
};

DraftEditor.propTypes = {
    content: PropTypes.string,
    placeholder: PropTypes.string,
    tools: PropTypes.shape({
        link: PropTypes.bool,
        font: PropTypes.bool,
    }).isRequired,
    onFocus: PropTypes.func,
    onBlur: PropTypes.func,
};

export default DraftEditor;
